
#include <aftermath/probability/empirical_measure.hpp>
#include <aftermath/algebra/permutation.hpp>
#include <aftermath/algebra/fraction.hpp>

#include <chrono>
#include <cstdint>
#include <iostream>
#include <random>
#include <string>

using namespace aftermath::probability;

template <typename T>
struct complex
{
    T real;
    T imaginary;

    complex() = default;

    complex(T real, T imaginary)
        : real(real), imaginary(imaginary)
    {

    }

    complex<T> operator +(const complex<T>& other) const
    {
        return complex<T>(this->real + other.real, this->imaginary + other.imaginary);
    }

    complex<T>& operator +=(const complex<T>& other)
    {
        this->real += other.real;
        this->imaginary += other.imaginary;
        return *this;
    }

    complex<T> operator *(double scalar) const
    {
        return complex<T>(this->real * scalar, this->imaginary * scalar);
    }

    complex<T> operator /(double scalar) const
    {
        return complex<T>(this->real / scalar, this->imaginary / scalar);
    }

    bool operator ==(const complex<T>& other) const
    {
        return this->real == other.real && this->imaginary == other.imaginary;
    }

    friend std::ostream& operator <<(std::ostream& os, const complex<T>& that)
    {
        os << that.real << " + " << that.imaginary << "i";
        return os;
    }
};

template <typename T>
complex<T> make_complex(T&& real, T&& imaginary)
{
    return complex<T>(real, imaginary);
}

namespace std
{
    template <typename T>
    struct hash<complex<T>>
    {
        typedef complex<T> argument_type;
        typedef std::size_t result_type;

        result_type operator()(argument_type const& x) const
        {
            auto h = std::hash<T>();
            return h(x.real) ^ h(x.imaginary);
        }
    };
}

namespace type_impl
{
    template <typename S, typename T = S>
    struct get_test_op2_result
    {
    private:
        template <typename X, typename Y>
        static auto check(X& x, Y& y) -> decltype(x + y);

        static std::false_type check(...);

    public:
        using type = decltype(check(std::declval<S&>(), std::declval<T&>()));
    };

    template <typename S, typename T = S>
    struct has_test_op2 : public std::integral_constant<bool, !std::is_same<
        typename get_test_op2_result<S, T>::type, std::false_type>::value>
    {
    };
}

void test_empirical_measure()
{
    empirical_measure<std::string> hs;
    hs.observe("A!");
    hs.observe("D");
    hs.observe("B");
    hs.observe("C++");

    std::cout << "String observations:" << std::endl;
    std::cout << hs << std::endl;
    std::cout << "----Min = " << hs.get_min() << std::endl;
    std::cout << "----Max = " << hs.get_max() << std::endl;
    std::cout << std::endl;

    empirical_measure<complex<double>> hc;

    hc.observe(make_complex(1.0, 2.0));
    hc.observe(make_complex(2.0, 2.8));
    hc.observe(make_complex(4.0, 3.0));
    hc.observe(make_complex(1.0, 5.0));

    std::cout << "Complex observations:" << std::endl;
    std::cout << hc << std::endl;
    std::cout << "-----Mean = " << hc.get_mean() << std::endl;
    std::cout << std::endl;


    typedef std::uniform_int_distribution<std::int32_t> distribution_type;
    typedef std::default_random_engine engine_type;
    distribution_type dist = distribution_type(0, 10);
    distribution_type count_dist = distribution_type(1, 10000);
    std::uint32_t seed = static_cast<std::uint32_t>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
    engine_type engine(seed);
    auto f = [](std::int32_t x) { return x * x; };

    empirical_measure<std::int32_t> h1;
    for (std::size_t i = 0UL; i < 50; i++) h1.observe(dist(engine), f(count_dist(engine)));

    empirical_measure<std::int32_t, std::size_t, double> h2;
    h2.observe(8, f(count_dist(engine)));

    auto htemp = h1;
    h1.merge(h2);
    h2.merge(htemp);

    std::cout << h1 << std::endl;

    bool is_x86 = (sizeof(void*) == 4);
    if (is_x86) std::cout << "Possible overflow in <std::int32_t> sum:" << std::endl;
    std::cout << "----Mean = " << h1.get_mean() << " \\pm " << h1.compute_standard_deviation() << std::endl;
    std::cout << "----CDF(8) = " << 100 * h1.cdf(8) << "%" << std::endl;
    std::cout << "----CDF(9) = " << 100 * h1.cdf(9) << "%" << std::endl;
    std::cout << "----20%-percentile = " << h1.percentile(0.2) << std::endl;
    std::cout << std::endl;

    if (is_x86) std::cout << "No overflow in <double> sum:" << std::endl;
    std::cout << "----Mean = " << h2.get_mean() << " \\pm " << h2.compute_standard_deviation() << std::endl;
    std::cout << "----CDF(8) = " << 100 * h2.cdf(8) << "%" << std::endl;
    std::cout << "----CDF(9) = " << 100 * h2.cdf(9) << "%" << std::endl;
    std::cout << "----20%-percentile = " << h2.percentile(0.2) << std::endl;
    std::cout << std::endl;
}

void test_permutation()
{
    std::vector<std::int32_t> x = { 1, -2, 4, 5, 0 };
    std::vector<std::string> y = { "1!", "-2-", "^4^", "=5=", "0..." };
    aftermath::algebra::permutation p(x);
    auto px = p.cref(x);
    auto py = p.ref(y);

    py[0].get() = "-3";

    for (std::size_t j : p) std::cout << j << " ";
    std::cout << std::endl;

    for (auto j : px) std::cout << j << " ";
    std::cout << std::endl;

    for (auto j : py) std::cout << std::string(j) << " ";
    std::cout << std::endl;
}

int main() // (int argc, char* argv[], char* envp[])
{
    //aftermath::algebra::fraction<std::size_t> q(1, 3);
    //std::size_t n = 50;
    //std::cout << ((q + n) * (std::size_t(1) - q)) << std::endl;
    //return 0;

    test_empirical_measure();

    //std::cout << "Press any key to continue . . ." << std::endl;
    return 0;
}
